/**
 * 该工具是用于调度存在众多依赖关系的任务执行次序的管理工具.
 * 
 * @version 1.0
 * @date 2014/07/04
 * 
 * org.snake.schedule.TaskManager
 * 		STATUS_RUNNING: "running"
 * 		STATUS_STOPPED: "stopped"
 * 
 * 		constructor: function TM()
 * 		addTask: function (task) 
 * 		cancel: function () * 	 	
 * 		debug: function (debug) 
 * 		getTask: function (taskID, status) 
 * 		notify: function (task) 
 * 		start: function () 
 * 		status: function () 
 * 		getCompletedTask: function(taskID) 
 * 		getErrorTask = function(taskID) 
 * 		getAvailableTask = function(taskID) 
 * 
 * org.snake.schedule.TaskStatus
 * 		{WAITING: "waiting", RUNNING: "running", ERROR: "error", COMPLETE: "complete"} 
 * 
 * org.snake.schedule.Task
 * 		constructor: function Task(taskID, taskManager)
 * 		addParameter: function (paramName, paramValue) 
 * 		addRelation: function (taskID) 
 *		getData: function () 
 *		getID: function () 
 *		getRelations: function () 
 *		getTaskManager: function () 
 *		process: function ()
 *		setData: function (data) 
 *		setParameters: function (parameters) 
 *		setTask: function (customTask) 
 *		status: function (taskStatus) 
 *      complete : function() 
 * 
 * org.snake.schedule.TaskFactory
 * 		createTask : function(taskID, taskManager, processFunction, parameters)
 *   
 */
window.org = window.org || {};
org.snake = org.snake || {};
org.snake.utils = org.snake.utils || {};
org.snake.schedule = org.snake.schedule || {};

org.snake.utils.extend = function(subClass, superClass) {
	var F = function() {};
	F.prototype = superClass.prototype;
	subClass.prototype = new F();
	subClass.prototype.constructor = subClass;

	subClass.superclass = superClass.prototype;
	if (superClass.prototype.constructor == Object.prototype.constructor) {
		superClass.prototype.constructor = superClass;
	}
};

/**
 * Class TaskManager
 * 
 * 根据任务的依赖关系自动调度任务执行，并监控任务执行的时间和状态
 * 
 * XXX Add some event listeners
 * 
 */
org.snake.schedule.TaskManager = (function(){
	
	org.snake.utils.extend (TM, Object);
	
	/* **************************
	 * variable definition block
	 * **************************/
	
	/** @static */
	TM.prototype.STATUS_STOPPED = "stopped";
	
	/** @static */
	TM.prototype.STATUS_RUNNING = "running";

	/** @private */
	TM.prototype._availableTasks;
	
	/** @private */
	TM.prototype._completeTasks;
	
	/** @private */
	TM.prototype._errorTasks;
	
	/** @private */
	TM.prototype._status;
	
	/** @private */
	TM.prototype._debug;
	
	/* **********************
	 * public method block
	 * *********************/
	
	/**
	 * start the task manager
	 * @public
	 */
	TM.prototype.start = function () {
		
		if(this._status == this.STATUS_RUNNING) {
			return;
		}
		
		if (this._availableTasks.length == 0) {
			this._status = this.STATUS_STOPPED;
			this._info("no task found!");
			return;
		}
		
		if (this._debug) {
			!console && alert("console is unavailable");
			this._info("started");
		}
		
		this._status = this.STATUS_RUNNING;
		
		for (var i = 0; i < this._availableTasks.length; i++) {
			var task = this._availableTasks[i];
			var relations = task.getRelations(); // Array<String>
			// 查找没有依赖的Task
			if (!relations || relations.length == 0) {
				this._runTask(task);
			}
		}
		
		
	};
	
	/**
	 * Called by the task to notify TM its work status changed
	 * 
	 * @param {org.snake.schedule.Task} task
	 * @public
	 */ 
	TM.prototype.notify = function(task) { 
		if (task.status() == org.snake.schedule.TaskStatus.RUNNING) {
			this._info("Task[" + task.getID() + "] is running");
		} 
		else if (task.status() == org.snake.schedule.TaskStatus.COMPLETE) {
			this._info("Task[" + task.getID() + "] completed");
			// move the complete task to the complete mapper
			this._moveCompleteTask(task);
			// find and execute all runnable tasks
			var runnableTasks = this._findRunnableTasks(task.getID());
			for (var i = 0; i < runnableTasks.length; i++) {
				this._runTask(runnableTasks[i]);
			}
		}
		else if (task.status() == org.snake.schedule.TaskStatus.ERROR) {
			this._warn("Task[" + task.getID() + "] is error");
			// move the error task to the error list
			this._moveErrorTask(task);
			// 清理存在依赖关系的available task
			var dependTasks = this._findDependentTasks(task.getID());
			for (var i = 0; i < dependTasks.length; i++) {
				this._warn("Task[" + dependTasks[i].getID() + "] cancelled");
				this._moveErrorTask(dependTasks[i]);
			}
		}
		
		if (this._availableTasks.length == 0) {
			this._status = this.STATUS_STOPPED;
			this._info("All tasks finished!");
		}
		
	};
	
	/**
	 * cancel all tasks execution <br>
	 * not implemented
	 * @public
	 */
	TM.prototype.cancel = function() {
		//XXX cancel all tasks execution
	};

	/**
	 * @return {String} running status of TaskManager
	 * @public
	 */
	TM.prototype.status = function() {
		return this._status;
	};
	
	/**
	 * add a new task
	 * @param {org.snake.schedule.Task} task
	 * @public
	 */
	TM.prototype.addTask = function(task) {
		if (!task || !task.getID()) {
			this._warn("task is invalid, add failed.");
			return;
		}
		if (this.getTask(task.getID())) {
			throw new Error("Found duplicate task: " + task.getID());
		}
		this._availableTasks.push(task);
	};
	TM.prototype.getCompletedTask = function(taskID) {
		return this.getTask(taskID, org.snake.schedule.TaskStatus.COMPLETE);
	};
	
	TM.prototype.getErrorTask = function(taskID) {
		return this.getTask(taskID, org.snake.schedule.TaskStatus.ERROR);
	};
	
	TM.prototype.getAvailableTask = function(taskID) {
		return this.getTask(taskID, org.snake.schedule.TaskStatus.WAITING);
	};
	
	
	/**
	 * @param {String} taskID the id of Task, is required
	 * @param {String} status the status of Task
	 * @return {org.snake.schedule.Task}
	 * @public
	 */
	TM.prototype.getTask = function(taskID, status) {
		
		if (!status || status == org.snake.schedule.TaskStatus.WAITING || status == org.snake.schedule.TaskStatus.RUNNING) {
			for (var i = 0; i < this._availableTasks.length; i++) {
				if (taskID == this._availableTasks[i].getID()) {
					return this._availableTasks[i];
				}
			}
		} 
		
		if (!status || status == org.snake.schedule.TaskStatus.COMPLETE) {
			return this._completeTasks[taskID];
		} 
		
		if (!status || status == org.snake.schedule.TaskStatus.ERROR) {
			
			for (var i = 0; i < this._errorTasks.length; i++) {
				if (taskID == this._errorTasks[i].getID()){
					return this._errorTasks[i];
				}
			}
			
		}
	};
	
	/**
	 * @param {boolean} debug 
	 */
	TM.prototype.debug = function(debug) {
		this._debug = debug;
	};
	
	/* **********************
	 * private method block
	 * *********************/
	
	/**
	 * execute a task
	 * 
	 * @param {org.snake.schedule.Task} task
	 */
	TM.prototype._runTask = function(task) {
		
		window.setTimeout(function(){
			if (task.status() == org.snake.schedule.TaskStatus.WAITING) {
				task.process();
			}
		});
	};
	
	/**
	 * @param {org.snake.schedule.Task} task
	 */
	TM.prototype._moveCompleteTask = function(task) {
		for (var i = 0; i < this._availableTasks.length; i++) {
			if(task.getID() == this._availableTasks[i].getID()){
				break;
			}
		}
		this._availableTasks.splice(i, 1);
		this._completeTasks[task.getID()] = task;
	};
	
	/**
	 * @param {org.snake.schedule.Task} task
	 */
	TM.prototype._moveErrorTask = function(task) {
		for (var i = 0; i < this._availableTasks.length; i++) {
			if(task.getID() == this._availableTasks[i].getID()){
				break;
			}
		}
		this._availableTasks.splice(i, 1);
		this._errorTasks[task.getID()] = task;
	};
	
	/**
	 * 返回已完成的 Task 的 ID 列表
	 * 
	 * @param {String} taskID
	 * @return {Array<String>} 
	 */
	TM.prototype._findRunnableTasks = function(taskID) {
		var result = [];
		for (var i = 0; i < this._availableTasks.length; i++) {
			var task = this._availableTasks[i];
			if (this._runnable(task)) {
				this._info("found runnable task: " + task.getID());
				result.push(task);	
			}
		}
		return result;
	};
	/**
	 * 找出所有存在依赖关系的Task
	 * 
	 * @param {String} taskID
	 * @return {Array}
	 * @private
	 */
	TM.prototype._findDependentTasks = function(taskID) {
		var result = [];
		for (var i = 0; i < this._availableTasks.length; i++) {
			var task = this._availableTasks[i];
			if (this._dependent(task, taskID)) {
				this._info("found dependent task: " + task.getID());
				result.push(task);	
			}
		}
		return result;
	};
	
	/**
	 * 检查task是否与指定Task存在依赖关系
	 * 
	 * @param {org.snake.schedule.Task} task
	 * @param {String} dependentTaskID
	 * @return {boolean} 如果存在依赖关系返回 true, 否则返回 false
	 */
	TM.prototype._dependent = function(task, dependTaskID) {
		var relations = task.getRelations(); // Array<String>
		for (var i = 0; i < relations.length; i++) {
			if (dependTaskID == relations[i]) {
				// 存在依赖关系
				return true;
			}
		}
		// 没有依赖关系
		return false;
	};
	
	/** 
	 * 检查task是否已经满足运行条件
	 * @param {org.snake.schedule.Task} task
	 * @return {boolean} 满足条件返回 true, 否则返回 false
	 */
	TM.prototype._runnable = function(task) {
		if (task.status() != org.snake.schedule.TaskStatus.WAITING) {
			return false;
		}
		var relations = task.getRelations(); // Array<String>
		for (var i = 0; i < relations.length; i++) {
			var dependentTaskID = relations[i];
			if (!this._completeTasks[dependentTaskID]) {
				// 运行条件不满足
				return false;
			}
		}
		// 全部依赖满足
		return true;
	};
	
	TM.prototype._info = function (message) {
		this._log("TM-INFO: " + message);
	};
	TM.prototype._warn = function (message) {
		this._log("TM-WARN: " + message);
	};
	TM.prototype._error = function (message) {
		this._log("TM-ERROR: " + message);
	};
	TM.prototype._log = function(message) {
		this._debug && console && console.log(message);
	};
	
	/**
	 * Task Manager
	 * @constructor
	 */
	function TM() {
		
		this._availableTasks = [];
		
		this._completeTasks = {};
		
		this._errorTasks = [];
		
		this._status = this.STATUS_STOPPED;
		
		this._debug = false;
	}
	
	return TM;
})();

/**
 * static class TaskStatus
 *  
 * 该类用于定义Task的几种运行时状态 
 * 
 */
org.snake.schedule.TaskStatus = {
	WAITING : "waiting",
	RUNNING : "running",
	ERROR : "error",
	COMPLETE : "complete"
};
/**
 * class Task
 * 
 * @param String taskID
 * @param TaskManager taskManager
 */
org.snake.schedule.Task = (function() {

	org.snake.utils.extend (Task, Object);
	
	/**
	 * @type String
	 * @private 
	 */
	Task.prototype._id;
	
	/**
	 * @type org.snake.schedule.TaskManager 
	 * @private 
	 */
	Task.prototype._taskManager;
	
	/**
	 * @type Array
	 * @private 
	 */
	Task.prototype._relations = [];
	
	/**
	 * @type Object
	 * @private 
	 */
	Task.prototype._parameters;
	
	/** 
	 * @type Object
	 * @private 
	 */
	Task.prototype._data; // cache
	
	/** 
	 * @type String
	 * @private 
	 */
	Task.prototype._status = org.snake.schedule.TaskStatus.WAITING;
	
	/**
	 * @type Function({org.snake.schedule.Task}) 
	 * @private 
	 */
	Task.prototype._task;
	
	/**
	 * 该方法用于执行用户定制的任务，由TaskManager调用
	 * @public
	 */
	Task.prototype.process = function() {
		if (this.status() == org.snake.schedule.TaskStatus.WAITING) {
			this.status(org.snake.schedule.TaskStatus.RUNNING);
			this._task(this);
		}
	};
	
	/**
	 * customTask 是用户定制的执行具体任务的函数，当所有任务结束时必须通过设置:
	 * 	 task.status(org.snake.schedule.TaskStatus.COMPLETE) 或
	 *   task.status(org.snake.schedule.TaskStatus.ERROR) 
	 * 通知所属 Task 和 TaskManager 任务完成状态
	 * 
	 * 如果任务需要缓存数据可以通过 setData(data) 将数据保存在task中，以供其他Task使用
	 * 
	 * 获取依赖任务和缓存的数据可以通过如下方法：
	 *   var dependTask = hostTask.getTaskManager().getCompletedTask(dependTaskID);
	 * 	 var cache = task.getData();
	 *   
	 * NOTE: this === hostTask , 在这里为 customTask 传递 hostTask 参数是为了避免使用者混淆 this 的指向 
	 * 
	 * @param {Function(org.snake.schedule.Task)} customTask
	 * @public
	 */
	Task.prototype.setTask = function(customTask) {
		this._task = customTask;
	};
	/**
	 * @param {Object} data 需要缓存的数据
	 */
	Task.prototype.setData = function(data) {
		this._data = data;
	};
	/**
	 * 如果taskID为空，返回自己的缓存数据，否则，返回指定 Task 的缓存数据
	 * 
	 * @param {String} taskID 
	 * @return {Object} 返回已完成的Task的缓存数据
	 */
	Task.prototype.getData = function(taskID) {
		if (taskID) {
			var t = this.getTaskManager().getCompletedTask(taskID);
			return t ? t.getData() : null;
		} else {
			return this._data;
		}
	};
	/**
	 * @param {String|TaskStatus.STATUS} status
	 * @return {String|TaskStatus.STATUS}
	 * @public
	 */
	Task.prototype.status = function(taskStatus) {
		if (taskStatus && taskStatus != this._status) {
			this._status = taskStatus;
			this._taskManager.notify(this);
		}
		return this._status;
	};
	
	/**
	 * change the status to 'COMPLETE'
	 * @public
	 */
	Task.prototype.complete = function() {
		this.status(org.snake.schedule.TaskStatus.COMPLETE);	
	};
	/**
	 * change the status to 'ERROR'
	 * @public
	 */
	Task.prototype.error = function() {
		this.status(org.snake.schedule.TaskStatus.ERROR);	
	};
	
	/**
	 * 设置参数表
	 * @param {Object} parameters 
	 * @return {org.snake.schedule.Task} this
	 * @public
	 */
	Task.prototype.setParameters = function(parameters) {
		this._parameters = parameters;
		return this;
	};
	/**
	 * 添加一个参数
	 * @param {String} paramName
	 * @param {Object} paramValue
	 * @public
	 */
	Task.prototype.addParameter = function(paramName, paramValue) {
		!(this._parameters) && (this._parameters = {});
		paramName && (this._parameters[paramName] = paramValue);
		return this;
	};
	/**
	 * 返回一个参数值
	 * @param {String} paramName
	 * @return {Object} value
	 * @public
	 */
	Task.prototype.getParameter = function(paramName) {
		if (this._parameters) {
			return this._parameters[paramName];
		}
		return null;
	}
	/**
	 * 添加一个依赖的TaskID
	 * 
	 * @param {String} taskID
	 * @return {org.snake.schedule.Task} this
	 * @public
	 */
	Task.prototype.addRelation = function(taskID) {
		taskID && taskID != this.getID() && this._relations.push(taskID);
		return this;
	};
	/**
	 * @return {Array<String>} 返回依赖的 TaskID 列表 
	 * @public
	 */	
	Task.prototype.getRelations = function() {
		return this._relations;
	};
	
	/**
	 * @return {String} return the taskID
	 * @public
	 */
	Task.prototype.getID = function() {
		return this._id;
	};
	
	/**
	 * @public
	 */
	Task.prototype.getTaskManager = function() {
		return this._taskManager;
	};
	/**
	 * @param {org.snake.schedule.TaskManager} tm
	 * @return {org.snake.schedule.Task} this
	 * @public
	 */
	Task.prototype.addToTaskManager = function (tm) {
		tm && tm.addTask(this);
		this._taskManager = tm;
		return this;
	}
	
	/**
	 * @param {String} taskID the id of task
	 * @param {org.snake.schedule.TaskManager} taskManager the instance of TaskManager
	 * @constructor
	 */
	function Task (taskID, taskManager) {
		this._id = taskID;
		this._taskManager = taskManager;
		this._relations = [];
		this._parameters = {};
		this._status = org.snake.schedule.TaskStatus.WAITING;
	};
	
	return Task;
})();


/**
 * static class TaskFactory
 */
org.snake.schedule.TaskFactory = {
	
	/* **********************
	 * Functions Definition 
	 * **********************/
	/**
	 * Create and return a new Task
	 * 
	 * @param String 		taskID 			required
	 * @param TaskManager 	taskManager 	required
	 * @param Function 		processFunction required
	 * @param Object 		parameters 		optional
	 */
	createTask : function(taskID, taskManager, customTask, parameters) {
		if ( !taskID )
			throw new Error("Task ID is invalid.");
		
		if ( !taskManager || ( !(taskManager instanceof org.snake.schedule.TaskManager) ) )
			throw new Error("TaskManager is invalid.");
			
		if (!customTask) 
			throw new Error("The function process is required.");
		
		var task = new org.snake.schedule.Task(taskID, taskManager);
		task.setTask(customTask);
		parameters && task.setParameters(parameters);
		
		taskManager.addTask(task);
		
		return task;
	}

};
//console.log(new org.snake.schedule.TaskManager);
//console.log(org.snake.schedule.TaskStatus);
//console.log(new org.snake.schedule.Task);
//console.log(org.snake.schedule.TaskFactory);
